// // Copyright (c) 2009 All Right Reserved // // Stephen Toub, Leslie Sanford // stoub@microsoft.com, jabberdabber@hotmail.com // 2009-01-01 // Contains ... // Classes for interop with Win32 MCI and low-level MIDI API using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using System.Text; using JetBrains.Annotations; namespace LargoCommon.Midi { /// Provides access to the Media Control Interface and other MIDI functionality. //// internal public static class MidiInternalDevices { //// Given in OutputDeviceBase //// private const int MOM_OPEN = 0x3C7; private const int MOM_CLOSE = 0x3C8; private const int MOM_DONE = 0x3C9; #region Fields //// internal const int CallbackFunction = 0x00030000; /// The default output MIDI device id. Midi Handle. private const int MidiMapper = -1; #endregion /// /// Synchronization Lock. /// private static readonly object ThisLock = new object(); //// Used for synchronization of all MIDI-related operations. //// private static readonly object midiLock = new object(); #region Fields /// The number of references to the currently open MIDI device. private static int numberReferences; //// = 0; #endregion #region Properties /// /// Gets Device Count (unused). /// [UsedImplicitly] public static int CountDevices => NativeMethods.MidiOutGetNumDevs(); /// /// Gets Handle of the midi device. /// public static MidiDeviceHandle MidiDeviceHandle { get; private set; } /// /// Gets a value indicating whether player was open. /// public static bool IsOpen => MidiDeviceHandle != null && MidiDeviceHandle.IsOpen; #endregion #region Midi Output device - open, reset (Win32 Midi Output Functions) /// /// Open the default MIDI device. /// /// /// This is necessary only when playing individual events. /// public static void OpenMidi() { lock (ThisLock) { // Open the MIDI device if it hasn't already been opened. if (numberReferences == 0) { InternalOpenMidi(); } numberReferences++; } } /// Opens the MIDI device without regard for whether it has already been opened. public static void InternalOpenMidi() { // Open the default MIDI device - Open the MIDI_MAPPER device (default). OpenMidiOut(MidiMapper); } /* Unfinished management of Midi devices /// /// Reset Midi Out (unused). /// [UsedImplicitly] public static void ResetMidiOut() { lock (thisLock) { // Reset the OutputDevice. int result = NativeMethods.midiOutReset(MidiDeviceHandle.Handle); if (result == MidiDeviceException.SystemNoError) { //// while (bufferCount > 0) { Monitor.Wait(thisLock); } } else { // Throw an exception. throw new OutputDeviceException(result); } } } */ #endregion #region Midi Output device - close (Win32 Midi Output Functions) /* Unfinished management of Midi devices /// Close the default MIDI device. [UsedImplicitly] public static void CloseMidi() { lock (thisLock) { // Close the MIDI device if no one else is using it if (numberReferences == 0) { return; } numberReferences--; if (numberReferences == 0) { InternalCloseMidi(); } } }*/ /// /// Prepares the midi. /// public static void PrepareMidi() { //// We can't play using MCI if we already have an open handle to the default //// MIDI device. As such, we'll temporarily close it if its open and then //// when we're done reopen it if it was open. if (IsOpen) { InternalCloseMidi(); } } /// Closes the MIDI device without regard for the reference count. public static void InternalCloseMidi() { // Close the MIDI device if it is open if (MidiDeviceHandle == null) { return; } MidiDeviceHandle.Dispose(); MidiDeviceHandle = null; numberReferences = 0; } /// Close the specified MIDI output device. /// Handle of the MIDI output device. public static void CloseMidiOut(int handle) { var result = NativeMethods.MidiOutClose(handle); //// MidiError ... this.midiHandle if (result != 0) { //// MidiSystemErrorNOERROR ThrowMciError(result, "Could not close MIDI out."); //// throw new Exception("Closing MIDI device failed with error " + result.ToString(CultureInfo.CurrentCulture)); } //// if (MidiDeviceHandle != null) { MidiDeviceHandle.Close(); } } #endregion #region Midi Output devices - number, outcaps /// /// Midi GetDeviceCaps. /// /// Device Id. /// Device Midi out Caps. /// Given size. /// Returns value. public static MidiError GetDeviceCaps(int deviceId, ref MidiOutcaps deviceLpCaps, int size) { return (MidiError)NativeMethods.MidiOutGetDevCaps(deviceId, ref deviceLpCaps, size); } /// /// Get Device Capabilities. /// /// Device Id. /// Returns value. [UsedImplicitly] public static MidiOutcaps GetDeviceCapabilities(int deviceId) { var caps = new MidiOutcaps(0, 0, 0, string.Empty, 0, 0, 0, 0, 0); // Get the device's capabilities. var result = NativeMethods.MidiOutGetDevCaps(deviceId, ref caps, Marshal.SizeOf(caps)); // If the capabilities could not be retrieved. if (result != (int)MidiError.SystemNoError) { // Throw an exception. throw new OutputDeviceException(result); } return caps; } #endregion #region MCI error /// Throws an exception based on the MCI error number. /// The MCI error number. /// The message to throw if an MCI message can't be retrieved. public static void ThrowMciError(int rv, string optionalMessage) { //// [VL] was private //// If there is an error, throw an exception with the best error description we can get. //// var error = GetMciError(rv) ?? "Could not close MIDI out."; //// error += optionalMessage; //// [VL] optionalMessage was unused //// Could not open MIDIOut (Device is already used...) #warning What here? //// throw new InvalidOperationException(error); } #endregion /// /// Midi GetNumberOfDevices. /// /// Returns value. public static short GetNumberOfDevices() { //// Int16 return NativeMethods.MidiOutGetNumDevs(); } #region Private Support /// Opens the specified MIDI output device. /// The ID of the MIDI output device to be opened. private static void OpenMidiOut(int deviceId) { var handle = 0; //// RunningStatusEnabled = false; var result = NativeMethods.MidiOutOpen(ref handle, deviceId, IntPtr.Zero, 0, 0); //// MidiError ... ref this.midiHandle, -1 //// midiOutProc = HandleMessage; //// int result = NativeMethods.MidiOutOpen(ref handle, deviceId, midiOutProc, 0, CallbackFunction); if (result != 0) { //// MidiDeviceException.SystemNoError ThrowMciError(result, "Could not open MIDI out."); //// throw new OutputDeviceException(result); //// throw new Exception("Opening MIDI device failed with error " + result.ToString(CultureInfo.CurrentCulture)); } MidiDeviceHandle = new MidiDeviceHandle(handle); } #endregion /* Unused #region Private Errors /// Gets the description for the given MCI error code. /// The error code for which we need an error description. /// The error description (or null if none exists). private static string GetMciError(int errorCode) { var buffer = new StringBuilder(255); // max string should be 128, so 255 just to be safe return NativeMethods.MciGetErrorString(errorCode, buffer, buffer.Capacity) == 0 ? null : buffer.ToString(); } #endregion */ //// Handles Windows messages. //// private static void HandleMessage(int handle, int msg, int instance, int param1, int param2) { } /// /// Native Methods. /// private static class NativeMethods { //// //// Prevents a default instance of the NativeMethods class from being created. //// //// private NativeMethods() {} #region Native Methods - Low-Level MIDI API - MidiOut Open, Reset (Win32 Midi Output Functions) /// The midiOutOpen function opens a MIDI output device for playback. /// Pointer to an HMIDIOUT handle. /// Identifier of the MIDI output device that is to be opened. /// Pointer to a callback function, an event handle, a thread identifier, or a handle of a window or thread called during MIDI playback to process messages related to the progress of the playback. /// User instance data passed to the callback. /// Callback flag for opening the device. /// Returns SystemNoError if successful or an error otherwise. [DllImport("winmm.dll", EntryPoint = "midiOutOpen", CharSet = CharSet.Ansi)] //// CharSet.Ansi [SuppressMessage("Microsoft.Portability", "CA1901:PInvokeDeclarationsShouldBePortable", MessageId = "return", Justification = "No other result found.")] public static extern int MidiOutOpen(ref int lphMidiOut, int uDeviceId, IntPtr dwCallback, int dwInstance, int dwCallbackFlag); //// dwFlags //// [DllImport("winmm.dll")] //// private static extern int midiOutOpen(ref int handle, int deviceID, MidiOutProc proc, int instance, int flags); /// /// Midis the out reset. /// /// The handle. /// Returns value. [DllImport("winmm.dll", EntryPoint = "midiOutReset"), UsedImplicitly] public static extern int MidiOutReset(int handle); #endregion #region Native Methods - Low-Level MIDI API - MidiOut Close (Win32 Midi Output Functions) /// The midiOutClose function closes the specified MIDI output device. /// Handle to the MIDI output device. /// Returns SystemNoError if successful or an error otherwise. [DllImport("winmm.dll", EntryPoint = "midiOutClose", CharSet = CharSet.Ansi)] //// CharSet.Ansi [SuppressMessage("Microsoft.Portability", "CA1901:PInvokeDeclarationsShouldBePortable", MessageId = "return", Justification = "No other result found.")] public static extern int MidiOutClose(int hMidiOut); //// [DllImport("winmm.dll")] //// private static extern int midiOutClose(int handle); #endregion #region NativeMethods - Devices /// /// MidiOut Get Number of Devices. /// /// Returns value. [DllImport("winmm.dll", EntryPoint = "midiOutGetNumDevs", CharSet = CharSet.Ansi)] [SuppressMessage("Microsoft.Portability", "CA1901:PInvokeDeclarationsShouldBePortable", MessageId = "return", Justification = "No other result found.")] public static extern short MidiOutGetNumDevs(); //// Int16 /// /// MidiOut Get Device Caps. /// /// Device Id. /// Midi out Caps. /// Given size. /// /// Returns value. /// [DllImport("winmm.dll", EntryPoint = "midiOutGetDevCapsA")] [SuppressMessage("Microsoft.Portability", "CA1901:PInvokeDeclarationsShouldBePortable", MessageId = "return", Justification = "No other result found.")] public static extern int MidiOutGetDevCaps(int uDeviceId, ref MidiOutcaps lpCaps, int uSize); //// [DllImport("winmm.dll")] //// protected static extern int midiOutGetDevCaps(int deviceID, //// ref MidiOutcaps caps, int sizeOfMidiOutCaps); #endregion /// The mciGetErrorString function retrieves a string that describes the specified MCI error code. /// Error code returned by the mciSendCommand or mciSendString function. /// Pointer to a buffer that receives a null-terminated string describing the specified error. /// Length of the buffer, in characters, pointed to by the previous parameter. /// Returns non-zero if successful or 0 if the error code is not known. /// Each string that MCI returns, whether data or an error description, can be a maximum of 128 characters. //// Use string (or System.String) for a const char*, but StringBuilder for a char*. [DllImport("winmm.dll", EntryPoint = "mciGetErrorStringA", CharSet = CharSet.Ansi)] [UsedImplicitly] //// CharSet.Ansi //// 11/2010 - MarshalAs public static extern int MciGetErrorString(int fdwError, StringBuilder lpszErrorText, int cchErrorText); //// public static extern int MciGetErrorString(int fdwError, [MarshalAs(UnmanagedType.LPWStr)]StringBuilder lpszErrorText, int cchErrorText); //// BestFitMapping: Turns on or off some optimization behavior when converting from Unicode to ANSI ////[DllImport("winmm.dll", CharSet = CharSet.Ansi, BestFitMapping = true, ThrowOnUnmappableChar = true)] ////[return: MarshalAs(UnmanagedType.Bool)] ////public static extern bool mciGetErrorString(uint mciError, [MarshalAs(UnmanagedType.LPStr)] System.Text.StringBuilder pszText, uint cchText); } } }